/*
 * tlog.c
 *
 *  Created on: 2011-08-16
 *      Author: Baron Wang
 */

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>

#include "tlog.h"

/* will be zero out in .data but still decleared                */
static int             __fd = 0;           /* log file fd       */
static int             __loglevel = L_ERR; /* default log level */
static logger          __logger = NULL;    /* external logger   */
static pthread_mutex_t __mutex;            /* a normal mutex    */
static int             __logger_init = 0;  /* init flag         */

#define CHK_INTERNAL_LV(_lv)        \
    if(_lv > __loglevel){           \
        return;                     \
    }

/* no mutex guard */
int log_get_lv(void){
    return __loglevel;
}

void log_set_lv(int lv){
    __loglevel = lv;
}

int init_logger(log_opt *opt){

/*    const char* logfile, int def_lvl, logger log){ */

    register int res;
#ifdef __USE_UNIX98
    pthread_mutexattr_t mutexattr;
#endif

    if(1 == __logger_init){
        printf("logger has been inited.\n");
        return -1;
    }

    if(NULL == opt){
        printf("the log opt is NULL.\n");
        return -1;
    }

    if(0 == opt->def_lvl){
        printf("invalid loglevel 0, should be used by system.\n");
        return -1;
    }

    if((NULL == opt->log) && (NULL == opt->logfile)){
        printf("there's no logger but the logfile is still NULL.\n");
        return -1;
    }

    if(NULL != opt->log){
        __logger = opt->log;
        __logger_init = 1;
        return 0;
    }

#ifdef __USE_UNIX98
    pthread_mutexattr_init(&mutexattr);
    (void)pthread_mutexattr_settype(&mutexattr, PTHREAD_MUTEX_ERRORCHECK);
    res = pthread_mutex_init(&__mutex, &mutexattr);
#else
    res = pthread_mutex_init(&__mutex, NULL);
#endif

#ifdef __USE_UNIX98
    /* anyway, we don't need it anymore */
    pthread_mutexattr_destroy(&mutexattr);
#endif

    if(0 != res){
        printf("failed to create the logger mutex.\n");
        return -1;
    }

#ifdef __USE_UNIX98
    if(EDEADLK == pthread_mutex_lock(&__mutex)) {
        printf("failed to lock the logger mutex.\n");
        return -1;
    }
#else
    pthread_mutex_lock(&__mutex);
#endif

    if(0 != access(opt->logfile, F_OK)){
        __fd = open(opt->logfile, O_WRONLY | O_CREAT);
        if(__fd >= 0){
            fchmod(__fd, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
        }
    }
    else{
        __fd = open(opt->logfile, O_WRONLY | O_APPEND);
    }

    if(__fd < 0){
        return -1;
    }

    pthread_mutex_unlock(&__mutex);

    __loglevel = opt->def_lvl;
    __logger_init = 1;

    return 0;
}

int is_init(void){
    return __logger_init;
}

void fini_logger(void){

    if(NULL != __logger){
        __logger = NULL;
        return;
    }

#ifdef __USE_UNIX98
    if(EDEADLK == pthread_mutex_lock(&__mutex)) {
        /* TODO LOG
         * some methods still use this mutex, try again sometime later; */
        return;
    }
#else
    pthread_mutex_lock(&__mutex);
#endif
    close(__fd);
    __logger_init = 0;
    __logger = NULL;
    pthread_mutex_unlock( & __mutex);
    pthread_mutex_destroy( & __mutex);
    return;
}

static __inline const char* __lvl_to_str(int lv){
    switch(lv){
        case L_DBG:
            return "--DBG-- ";
        case L_RTM:
            return "--RTM-- ";
        case L_CHK:
            return "--CHK-- ";
        case L_WRN:
            return "--WRN-- ";
        case L_ERR:
            return "--ERR-- ";
        case L_CRI:
            return "--CRI-- ";
        case L_ASS:
            return "--ASS-- ";
        case L_SYS:
            return "--SYS-- ";
        default:
            return " ";
    }
}

static void __inner_log(int lv, const char * file, int line,
                const char* func, const char* msg){

    char buff[512];
    struct timeval t;
    struct tm tt;
    register const char* strip_filename;
    register int buf_offset = 0;

    if(0 == __logger_init){
        return;
    }
    /* we don't guard the __loglevel because it is less irrelant */
    CHK_INTERNAL_LV(lv)

    if(gettimeofday(&t, NULL) != 0){
        buf_offset += snprintf(buff + buf_offset, sizeof(buff) - buf_offset,
                        "invalid data time, ");
    }
    else{
        localtime_r(&t.tv_sec, &tt);
        buf_offset += snprintf(buff + buf_offset, sizeof(buff) - buf_offset,
            "%s%02d/%02d/%02d, %02d:%02d:%02d.%03ld",
            __lvl_to_str(lv),
            tt.tm_mon + 1, tt.tm_mday, tt.tm_year % 100, tt.tm_hour,
            tt.tm_min, tt.tm_sec, t.tv_usec / 1000);
    }

    /* find the last 'l' to make log much pretty */
    strip_filename = strrchr(file, '/');
    if(NULL == strip_filename){
        /* if there's no slash, we should redirect it */
        strip_filename = file;
    }
    ++strip_filename;

    buf_offset += snprintf(buff + buf_offset, sizeof(buff) - buf_offset,
                    ", File:%10s, Line: %5u, Function :%15s  ", strip_filename,
                    line, func);

    if(buf_offset < 0 || buf_offset >= sizeof(buff)){
        printf("logger buffer is too small!\n");

#ifdef __USE_UNIX98
        if(EDEADLK == pthread_mutex_lock(&__mutex)) {
            /* TODO LOG
                make buffer to save log data; */
            return;
        }
#else
        pthread_mutex_lock(&__mutex);
#endif
        write(__fd, buff, sizeof(buff));
        pthread_mutex_unlock(&__mutex);
        return;
    }

    buf_offset += snprintf(buff + buf_offset, sizeof(buff) - buf_offset, "%s\n",
                    msg);

    if(buf_offset < 0 || buf_offset >= sizeof(buff)){
        printf("logger buffer is too small!\n");

#ifdef __USE_UNIX98
        if(EDEADLK == pthread_mutex_lock(&__mutex)) {
            /* TODO LOG
                make buffer to save log data; */
            return;
        }
#else
        pthread_mutex_lock(&__mutex);
#endif
        write(__fd, buff, sizeof(buff));
        pthread_mutex_unlock(&__mutex);
        return;
    }

    /*
     * We can do something here, for example, use pthread_mutex_trylock to
     * try to lock the mutex, if failed, means that the logger file is busy
     *
     * we can store the log into a buffer or a memory pool, or send to a
     * server that can buffer it.
     *
     * it is based on implementation
     *
     */
    pthread_mutex_lock(&__mutex);
    write(__fd, buff, buf_offset);
    pthread_mutex_unlock(&__mutex);

    return;
}

void log_msg(int lv, const char* file, int line,
                const char* func, const char *format, ...){

    register int ret;
    char buff[512];
    va_list arglist;

    if(0 == __logger_init){
        return;
    }
    /* we don't guard the __loglevel because it is less irrelant */
    CHK_INTERNAL_LV(lv)

    va_start(arglist, format);
    ret = vsnprintf(buff, sizeof(buff), format, arglist);
    if ((ret < 0) || (ret >= sizeof(buff))){

        if(NULL != __logger){
            __logger(L_ASS, "ERROR, the LOGGER buffer overflow.");
        }else{
            __inner_log(L_ASS, file, line, func,
                "ERROR, the LOGGER buffer overflow.");
        }
    }
    va_end(arglist);

    if(NULL != __logger){
        __logger(lv, "%s", buff);
        return;
    }

    __inner_log(lv, file, line, func, buff);
    return;
}

void dump_msg(int lv, const char* file, int line,
                const char* func, const char* raw, int len){

    char print_buf[16 * 74 + 1];char *pBuf = print_buf + 1;
    int index = 0;
    int lineno = 0;
    char *p1, *p2;

    int i;

    if(0 == __logger_init){
        return;
    }
    CHK_INTERNAL_LV(lv)

    /* insert a carrier to make log more clear */
    print_buf[0] = '\n';

    /* Maximun dump 1024 bytes. */
    if (len > 256){
        len = 256;
    }

    while (index < len){
        p1 = pBuf + 7;
        p2 = pBuf + 57;

        ++lineno;
        sprintf(pBuf, "%04xh: ", index);

        for (i = 0; i < 16; ++i){
            if (index >= len){
                sprintf(p1, "   ");
                *p2 = ' ';
            }
            else{
                sprintf(p1, "%02X ", raw[index]);
                *p2 = isprint((unsigned char)raw[index]) ? raw[index] : '.';
                index ++;
            }
            p1 += 3;
            ++p2;
        }

        pBuf[55] = ';';
        pBuf[56] = ' ';
        pBuf[73] = '\n';
        pBuf += 74;
    }
    print_buf[lineno * 74 - 1] = '\0';

    if(__logger != NULL){
        __logger(lv, "%s", print_buf);
        return;
    }

    __inner_log(lv, file, line, func, print_buf);
}

void log_anyway(const char *format, ...){

    char buff[1024];
    register int ret = 0;
    va_list arglist;

    if(0 == __logger_init){
        return;
    }

    va_start(arglist, format);
    ret = vsnprintf(buff, sizeof(buff), format, arglist);
    if ((ret < 0) || (ret >= sizeof(buff))){

        if(NULL != __logger){
            __logger(L_ASS, "ERROR, the LOGGER buffer overflow.");
        }else{
            __inner_log(L_ASS, __FILE__, __LINE__, __FUNCTION__,
                "ERROR, the LOGGER buffer overflow.");
        }
    }
    va_end(arglist);

    pthread_mutex_lock(&__mutex);
    write(__fd, buff, ret);
    pthread_mutex_unlock(&__mutex);

    return;
}
